﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WebLayer;
using WebPageParser;
using TableParsers;
using System.Diagnostics;

namespace SpeakupBackend.Classes
{
    static public class ClassListFetcher
    {
        static public ClassList Fetch(LoginToken loginToken)
        {
            WebPageCache cache = new WebPageCache();
            cache.LoginToken = loginToken;
            return Fetch(cache);
        }

        static public ClassList Fetch(WebPageCache cache)
        {
            /* Get Past history: attended, missed, cancelled classes*/
            ClassList pastList = FetchPastClasses(cache);
            ClassList futureList = FetchFutureClasses(cache);

            pastList.AddRange(futureList);
            return pastList;
        }

        static public ClassList FetchPastClasses(LoginToken loginToken)
        {
            WebPageCache cache = new WebPageCache();
            cache.LoginToken = loginToken;
            return FetchPastClasses(cache);
        }

        static public ClassList FetchPastClasses(WebPageCache cache)
        {
            ClassList list = new ClassList();

            AttendanceTable attendanceTable = AttendanceTable.Fetch(cache);
            CancelledTable cancelledTable = CancelledTable.Fetch(cache);

            foreach (AttendanceTableEntry entry in attendanceTable)
            {
                ClassBuilder speakupClass = new ClassBuilder();
                speakupClass.ClassId = 0;

                if (entry.Present)
                {
                    speakupClass.ClassStatus = ClassStatus.Attended;
                }
                else
                {
                    speakupClass.ClassStatus = ClassStatus.Missed;
                }

                speakupClass.ClassName = entry.ClassName;
                speakupClass.ClassTime = entry.ClassTime;
                speakupClass.ClassUnit = entry.Unit;
                list.Add(speakupClass.Create());
            }

            foreach (CancelledTableEntry entry in cancelledTable)
            {
                ClassBuilder speakupClass = new ClassBuilder();
                speakupClass.ClassId = 0;
                speakupClass.ClassName = entry.ClassName;
                speakupClass.ClassTime = entry.ClassTime;
                speakupClass.ClassStatus = ClassStatus.Cancelled;
                speakupClass.ClassUnit = entry.Unit;
                list.Add(speakupClass.Create());
            }
            
            return list;
        }

        static public ClassList FetchFutureClasses(LoginToken loginToken)
        {
            WebPageCache cache = new WebPageCache();
            cache.LoginToken = loginToken;
            return FetchFutureClasses(cache);
        }

        static public ClassList FetchFutureClasses(WebPageCache cache)
        {
            ClassList list = new ClassList();
            HashSet<int> ids = new HashSet<int>();

            BookTable bookTable = BookTable.Fetch(cache);
            ReservedTable reservedTable = ReservedTable.Fetch(cache);
            WaitListTable waitListTable = WaitListTable.Fetch(cache);

            foreach (ReservedTableEntry entry in reservedTable)
            {
                ClassBuilder speakupClass = new ClassBuilder();
                speakupClass.ClassId = entry.ClassId;
                speakupClass.ClassName = entry.ClassName;
                speakupClass.ClassTime = entry.ClassTime;
                speakupClass.ClassStatus = ClassStatus.Reserved;                

                ids.Add(entry.ClassId);
                list.Add(speakupClass.Create());
            }

            foreach (WaitListTableEntry entry in waitListTable)
            {
                ClassBuilder speakupClass = new ClassBuilder();
                speakupClass.ClassId = entry.ClassId;
                speakupClass.ClassName = entry.ClassName;
                speakupClass.ClassTime = entry.ClassTime;
                speakupClass.ClassStatus = ClassStatus.ReservedWaitList;

                ids.Add(entry.ClassId);
                list.Add(speakupClass.Create());
            }

            /* zero ids should be ignored */
            if (ids.Contains(0))
            {
                ids.Remove(0);
            }
            
            foreach (Class zeroIdClass in list.FindAll( cl => cl.Id == 0) )
            {
                BookTableEntry entry = 
                    bookTable.Find(cl =>
                    cl.ClassName.FullName == cl.ClassName.FullName &&
                    cl.ClassName.Kind == zeroIdClass.Name.Kind &&
                    cl.ClassName.Type == zeroIdClass.Name.Type &&
                    cl.ClassTime.Date == zeroIdClass.Time.Date &&
                    cl.ClassTime.Begin == zeroIdClass.Time.Begin &&
                    cl.ClassTime.End == zeroIdClass.Time.End);

                if(entry != null)
                {
                    zeroIdClass.Id = entry.ClassId;                    
                    ids.Add(entry.ClassId);
                }
            }

            foreach (BookTableEntry entry in bookTable)
            {
                // if such an id is already used, we 
                // do not list this class any more
                if (ids.Contains(entry.ClassId))
                {
                    continue;
                }

                ClassBuilder speakupClass = new ClassBuilder();
                speakupClass.ClassId = entry.ClassId;
                speakupClass.ClassName = entry.ClassName;
                speakupClass.ClassTime = entry.ClassTime;
                
                if (entry.Available)
                {
                    speakupClass.ClassStatus = ClassStatus.Available;                    
                }
                else
                {
                    speakupClass.ClassStatus = ClassStatus.AvailableWaitList;
                }

                list.Add(speakupClass.Create());
            }

            //TODO: test all ways to connect wait list classes with
            FixFututreClassesList(list);
            return list;
        }

        static void FixFututreClassesList(ClassList list)
        {
            List<Class> reservedWaitListClasses = 
                                      (from c in list
                                       where c.CurrentStatus == ClassStatus.ReservedWaitList
                                       select c).ToList();

            /* find and connect alternative wait classes */
            foreach (Class reserved in reservedWaitListClasses)
            {
                List<Class> availableWaitListClasses =
                                           (from c in list
                                           where c.CurrentStatus == ClassStatus.AvailableWaitList &&
                                                 c.Time.Date == reserved.Time.Date &&
                                                 c.Name== reserved.Name
                                           select c).ToList();

                if (availableWaitListClasses.Count > 0)
                {
                    reserved.Id = availableWaitListClasses[0].Id;
                    reserved.Time = availableWaitListClasses[0].Time;
                    list.Remove(availableWaitListClasses[0]);
                }
            }
        }


        static int ClassStatusPriority(ClassStatus classStatus)
        {
            //three priority classes:
            //priority 3 = Reserved, ReservedWaitList, 
            //priority 2 = Available, AvailableWaitList
            //priority 1 = others

            if (classStatus.IsReserved())
            {
                return 3;
            }

            if (classStatus.IsAvailable())
            {
                return 2;
            }

            return 1;
        }

        /// <summary>
        /// Removes class duplicates using prioritizing.
        /// I.e. classes with higher lower priority are removed.
        /// </summary>
        public static void RemoveClassDuplicates(ClassList list)
        {
            List<int> removalIndecies = new List<int>();
            Comparison<int> descedingComp = new Comparison<int>( (a,b) => 
                {
                    return b-a;
                });

            for (int i = 0; i < list.Count; i++)
            {
                removalIndecies.Clear();

                for (int j = i + 1; j < list.Count; j++)
                {                    
                    if (list[i].Time == list[j].Time && 
                        list[i].Name.Type == list[j].Name.Type &&
                        list[i].Name.Name == list[j].Name.Name)                         
                    {
                        int priority_i = ClassStatusPriority(list[i].CurrentStatus);
                        int priority_j = ClassStatusPriority(list[j].CurrentStatus);
                        
                        if (priority_i < priority_j)
                        {
                            Class tmp = list[i];
                            list[i] = list[j];
                            list[j] = tmp;
                        }

                        removalIndecies.Add(j);
                    }
                }


                removalIndecies.Sort(descedingComp);
                foreach (int remIndex in removalIndecies)
                {
                    list.RemoveAt(remIndex);
                }
            }
        }
       
    }
}
